#include "PollPoller.h"

#include "../tcp/Channel.h"
#include "../../base/log/Logging.h"

#include <sys/poll.h>
#include <cassert>

PollPoller::PollPoller(EventLoop *loop) : Poller(loop) {}

PollPoller::~PollPoller() = default;

Timestamp PollPoller::poll(int timeoutMs, ChannelList *activeChannels) {
    LOG_TRACE << "PollPoller::poll调用监听, 监听数量 : " << channels_.size();
    int numEvents = ::poll(&*pollfds_.data(), pollfds_.size(), timeoutMs);
    // 调用poll函数, 因为vector是STL保证的底层以数组形式保存的存在
    // 所以直接获取数组元素的首地址就是我们要传入的事件数组的地址了
    // 获取当前活动IO事件, 然后填充后传入到activeChannels中去
    int savedErrno = errno;
    Timestamp now(Timestamp::now());
    if (numEvents) {
        fillActiveChannels(numEvents, activeChannels);
        // 当发现文件描述符上的事件发生的时候就调用对应的程序去处理
    } else if (numEvents == 0) {
        // 监听失败, 进行日志记录
        LOG_TRACE << "EPollPoller::poll 调用监听无事发生";
    } else {
        if (savedErrno != EINTR) {
            errno = savedErrno;
            LOG_SYSERR << "PollPoller::poll()";
        }
    }
    return now;
    // 这里应该是返回一个完成的事件戳的
}


void PollPoller::fillActiveChannels(int numEvents, ChannelList *activeChannels) const {
    for (auto pfd = pollfds_.begin(); pfd != pollfds_.end() && numEvents > 0; ++pfd) {
        if (pfd->revents > 0) {
            --numEvents;
            auto ch = channels_.find(pfd->fd);
            assert(ch != channels_.end());
            // 基本的校验检查
            Channel *channel = ch->second;
            assert(channel->fd() == pfd->fd);
            channel->set_revents(pfd->revents);
            // 设置channel的文件描述符事件
            activeChannels->push_back(channel);
        }
    }
}

void PollPoller::updateChannel(Channel *channel) {
    Poller::assertInLoopThread();
    if (channel->index() < 0) {
        // 如果发现对应的文件描述符管理的没有被添加到pollfds_数组中, 就会进行添加操作
        assert(channels_.find(channel->fd()) == channels_.end());
        struct pollfd pfd;
        pfd.fd = channel->fd();
        pfd.events = static_cast<short>(channel->events());
        pfd.revents = 0;
        // 这里就是配置对应的文件描述符事件了
        pollfds_.push_back(pfd);
        // 添加到pollfds_数组中, 用于后续观察
        int idx = static_cast<int>(pollfds_.size()) - 1;
        channel->set_index(idx);
        // 获取对应的索引, 并设置到对应的channel中
        channels_[pfd.fd] = channel;
        // 将对应的文件描述符与回调处理类添加到map集合中
    } else {
        assert(channels_.find(channel->fd()) != channels_.end());
        assert(channels_[channel->fd()] == channel);
        int idx = channel->index();
        assert(0 <= idx && idx < static_cast<int>(pollfds_.size()));
        struct pollfd &pfd = pollfds_[idx];
        // 通过对应的索引获取文件描述符
        assert(pfd.fd == channel->fd() || pfd.fd == -channel->fd() - 1);
        // 校验Channel 和 poll中保存的文件描述符是否相同, 后面`pfd.fd == -channel->fd() - 1`是标识忽略此文件描述符的意思
        pfd.fd = channel->fd();
        pfd.events = static_cast<short>(channel->events());
        // 设置文件描述符的事件
        pfd.revents = 0;
        // 重置监听的事件
        if (channel->isNoneEvent()) {
            pfd.fd = -channel->fd() - 1;
            // FIXME: 这里实现的是对文件描述符的忽略, 采用 -fd - 1的方式
            // FIXME: 不直接设置为 -1或其他标识的原因是因为还需要利用这个文件描述符在 channels_集合中走到对应的channels_;
        }
        // 这里是如果对应的事件处理函数不想再关心这个文件描述符的话了, 就将对应的fd设置为 -1, 让poll忽略这个文件描述符
    }
}

void PollPoller::removeChannel(Channel *channel) {
    PollPoller::assertInLoopThread();

    LOG_TRACE << "PollPoller::removeChannel调用, fd [" << channel->fd()
              << "] 被移除";

    assert(channels_.find(channel->fd()) != channels_.end());
    assert(channels_[channel->fd()] == channel);
    assert(channel->isNoneEvent());
    int idx = channel->index();
    assert(0 <= idx && idx < static_cast<int>(pollfds_.size()));
    const struct pollfd &pfd = pollfds_[idx];
    (void) pfd;

    assert(pfd.fd == -channel->fd() - 1 && pfd.events == channel->events());
    size_t n = channels_.erase(channel->fd());
    // 从集合中删除channels
    assert(n == 1);
    (void) n;
    if (static_cast<size_t>(idx) != pollfds_.size() - 1) {
        int channelAtEnd = pollfds_.back().fd;
        iter_swap(pollfds_.begin() + idx, pollfds_.end() - 1);
        // 交换两个文件描述符的channel的位置, 这样可以直接弹出
        if (channelAtEnd < 0) {
            channelAtEnd = -channelAtEnd - 1;
            // 如果末尾的文件描述符对应的事件是不关心的, 我们就需要这样来找到对应的文件描述符
        }
        channels_[channelAtEnd]->set_index(idx);
        // 将交换的原末尾的元素channel下标更换下
    }
    pollfds_.pop_back();
}
